package com.cq.hd.app.biz;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cq.hd.app.config.LockUtils;
import com.cq.hd.app.config.WxConfig;
import com.cq.hd.app.mq.UserLoginRecordMsgProvider;
import com.cq.hd.app.vo.LoginVo;
import com.cq.hd.common.constant.Constant;
import com.cq.hd.common.constant.RedisKeyConstant;
import com.cq.hd.common.entity.AppLoginUser;
import com.cq.hd.common.entity.BusinessInfo;
import com.cq.hd.common.entity.UserInfo;
import com.cq.hd.common.entity.mq.UserLoginRecordMsg;
import com.cq.hd.common.entity.wx.dto.WxLoginMobileDto;
import com.cq.hd.common.entity.wx.vo.Code2SessionVo;
import com.cq.hd.common.enums.ResponseEnums;
import com.cq.hd.common.response.Throw;
import com.cq.hd.common.utils.*;
import com.cq.hd.member.api.TbAppUserApi;
import com.cq.hd.member.api.TbBusinessApi;
import com.cq.hd.member.api.dto.LoginDto;
import com.cq.hd.member.api.dto.MobileLoginDto;
import com.cq.hd.member.api.vo.AppUserVo;
import com.cq.hd.member.api.vo.BusinessPageVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class AuthBiz {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private WxConfig wxConfig;

    @Autowired
    private TbAppUserApi appUserApi;

    @Autowired
    private TbBusinessApi businessApi;

    @Autowired
    private UserLoginRecordMsgProvider userLoginRecordMsgProvider;

    /**
     * 手机号密码登录
     * @param mobileLoginDto
     * @param request
     * @return
     */
    public LoginVo loginByMobile(MobileLoginDto mobileLoginDto, HttpServletRequest request) {
        String mobile = mobileLoginDto.getMobile();
        String userPass = mobileLoginDto.getUserPass();

        // 获取登录IP地址
        String ipAddr = IpUtil.getIpAddr(request);

        // 对手机号加锁
        String lockKey = MessageFormat.format(Constant.LOCK_FORMAT, "LoginByMobile:" + mobile);
        try {
            LockUtils.blockLock(lockKey, Constant.AUTO_UNLOCK_SECOND);

            // 通过手机号查询用户信息
            AppUserVo appUserVo = appUserApi.getByMobile(mobile).unpack();
            if (appUserVo == null) {
                Throw.isBusinessException(ResponseEnums.USER_NOT_FOUND);
            }

            // 校验是否被封禁
            if (appUserVo.getState() == 2) {
                Throw.isBusinessException(ResponseEnums.USER_NOT_FOUND);
            }

            // 校验密码是否正确
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            if (!encoder.matches(userPass, appUserVo.getLoginPass())) {
                Throw.isBusinessException(ResponseEnums.USER_PWD_FAILED);
            }

            UserInfo userInfo = new UserInfo();
            BeanUtils.copyProperties(appUserVo, userInfo);
            String userIdRedis = String.valueOf(appUserVo.getId());

            // 查找商家信息，存在则压入到缓存中
            BusinessPageVo businessPageVo = businessApi.getByMobile(userInfo.getMobile()).unpack();
            if (businessPageVo != null) {
                BusinessInfo businessInfo = new BusinessInfo();
                BeanUtils.copyProperties(businessPageVo, businessInfo);
                userInfo.setBusinessInfo(businessInfo);
            }

            userInfo.setPwdSetState(!StringUtils.isBlank(appUserVo.getLoginPass()));

            // 将用户详细信息缓存到Redis
            redisUtil.set(String.format(RedisKeyConstant.USER_INFO_KEY, userIdRedis), JSON.toJSONString(userInfo), Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

            // 生成随机密钥
            String logKey = StringUtils.getRandomNumString(8);
            // 生成token
            String lookupLogKey = UUID.randomUUID().toString().replaceAll("-", "");
            String token = JwtUtil.createToken(logKey, userIdRedis, lookupLogKey);
            redisUtil.set(String.format(RedisKeyConstant.USER_LOGIN_KEY, lookupLogKey), logKey, Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

            // 发送MQ消息，异步添加用户登录记录
            UserLoginRecordMsg userLoginRecordMsg = new UserLoginRecordMsg();
            userLoginRecordMsg.setUserId(Long.parseLong(userIdRedis));
            userLoginRecordMsg.setLoginDate(LocalDateTime.now());
            userLoginRecordMsg.setLoginIp(ipAddr);
            // 登录方式，默认1:手机号登录 2:微信登录
            userLoginRecordMsg.setLoginMode(1);
            userLoginRecordMsg.setCreateTime(LocalDateTime.now());
            userLoginRecordMsgProvider.sendMsg(userLoginRecordMsg);

            LoginVo loginVo = new LoginVo();
            loginVo.setToken(token);
            loginVo.setIsRegisterFlag(false);

            return loginVo;
        } finally {
            LockUtils.unlock(lockKey);
        }
    }

    public LoginVo login(LoginDto loginDto, HttpServletRequest request) {
        String openId = loginDto.getOpenId();
        if (StringUtils.isBlank(openId)) {
            log.error("login注册失败，openId为空");
            Throw.isBusinessException("请先授权");
        }

        Object mobileObj = redisUtil.hGet(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openId), "mobile");
        if (mobileObj == null) {
            log.error("login注册失败，mobile为空");
            Throw.isBusinessException("请先授权");
        }

        String mobile = (String) mobileObj;
        loginDto.setMobile(mobile);
        // 获取登录IP地址
        String ipAddr = IpUtil.getIpAddr(request);
        loginDto.setIpAddr(ipAddr);
        boolean isRegisterFlag = false;
        UserInfo userInfo = new UserInfo();

        // 根据userMobile从缓存中获取userId
        String userIdRedis = redisUtil.get(String.format(RedisKeyConstant.USER_MOBILE_KEY, mobile));
        if (StringUtils.isNullOrEmpty(userIdRedis)) {
            // 通过手机号查询用户信息
            AppUserVo appUserVo = appUserApi.getByMobile(mobile).unpack();
            // 判断用户是否是新用户
            if (appUserVo != null) {
                if (appUserVo.getState() == 2) {
                    Throw.isBusinessException(ResponseEnums.USER_NOT_FOUND);
                }

                BeanUtils.copyProperties(appUserVo, userInfo);
                userIdRedis = String.valueOf(appUserVo.getId());

                userInfo.setPwdSetState(!StringUtils.isBlank(appUserVo.getLoginPass()));
            } else {
                Object unionidObj = redisUtil.hGet(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openId), "unionid");
                if (unionidObj != null) {
                    loginDto.setUnionId((String) unionidObj);
                }

                // 用户注册
                appUserVo = appUserApi.register(loginDto).unpack();
                if (appUserVo == null) {
                    log.warn("【{}】用户注册失败", mobile);
                    Throw.isBusinessException(ResponseEnums.USER_REGISTER_FAILED);
                }
                BeanUtils.copyProperties(appUserVo, userInfo);
                userIdRedis = String.valueOf(appUserVo.getId());
                isRegisterFlag = true;
            }

            // 将userId缓存到Redis
            redisUtil.set(String.format(RedisKeyConstant.USER_MOBILE_KEY, mobile), userIdRedis);
        } else {
            //根据userId获取用户信息
            String userInfoRedis = redisUtil.get(String.format(RedisKeyConstant.USER_INFO_KEY, userIdRedis));
            if (StringUtils.isNullOrEmpty(userInfoRedis)) {
                //缓存用户信息
                AppUserVo appUserVo = appUserApi.getById(Long.parseLong(userIdRedis)).unpack();
                if (appUserVo == null || appUserVo.getState() == 2) {
                    log.warn("【{}】用户登录失败", mobile);
                    Throw.isBusinessException(ResponseEnums.USER_NOT_FOUND);
                }
                BeanUtils.copyProperties(appUserVo, userInfo);

                userInfo.setPwdSetState(!StringUtils.isBlank(appUserVo.getLoginPass()));
            } else {
                userInfo = JSON.parseObject(userInfoRedis, UserInfo.class);
            }
        }

        // 查找商家信息，存在则压入到缓存中
        BusinessPageVo businessPageVo = businessApi.getByUserId(userInfo.getId()).unpack();
        if (businessPageVo != null) {
            BusinessInfo businessInfo = new BusinessInfo();
            BeanUtils.copyProperties(businessPageVo, businessInfo);
            userInfo.setBusinessInfo(businessInfo);
        }

        // 将用户详细信息缓存到Redis
        redisUtil.set(String.format(RedisKeyConstant.USER_INFO_KEY, userIdRedis), JSON.toJSONString(userInfo), Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

        // 生成随机密钥
        String logKey = StringUtils.getRandomNumString(8);
        // 生成token
        String token = JwtUtil.createToken(logKey, userIdRedis);
        redisUtil.set(String.format(RedisKeyConstant.USER_LOGIN_KEY, userIdRedis), logKey, Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

        // 发送MQ消息，异步添加用户登录记录
        UserLoginRecordMsg userLoginRecordMsg = new UserLoginRecordMsg();
        userLoginRecordMsg.setUserId(Long.parseLong(userIdRedis));
        userLoginRecordMsg.setLoginDate(LocalDateTime.now());
        userLoginRecordMsg.setLoginIp(ipAddr);
        // 登录方式，默认1:手机号登录 2:微信登录
        userLoginRecordMsg.setLoginMode(2);
        userLoginRecordMsg.setCreateTime(LocalDateTime.now());
        userLoginRecordMsgProvider.sendMsg(userLoginRecordMsg);

        LoginVo loginVo = new LoginVo();
        loginVo.setToken(token);
        loginVo.setIsRegisterFlag(isRegisterFlag);

        return loginVo;
    }

    public boolean logout(AppLoginUser appLoginUser) {
        redisUtil.del(String.format(RedisKeyConstant.USER_LOGIN_KEY, appLoginUser.getUserId()));
        return true;
    }

    public Code2SessionVo code2session(String code, HttpServletRequest request) {
        String result = wxConfig.getOpenIdAndSessionKey(code);
        JSONObject openIdAndSessionKey = JSON.parseObject(result);
        String openid = openIdAndSessionKey.getString("openid");
        String unionid = openIdAndSessionKey.getString("unionid");
        log.info("code2session----openid:{}, unionid:{}", openid, unionid);
        String sessionKey = openIdAndSessionKey.getString("session_key");
        if (StringUtils.isBlank(openid) || StringUtils.isBlank(sessionKey)) {
            log.error("code2session获取失败，result:{}", result);
            Throw.isBusinessException("登录失败");
        }

        // 根据openId查库，存在表示已经注册，返回token，否则返回openId
        AppUserVo appUserVo = appUserApi.getByOpenId(openid).unpack();
        if (appUserVo != null) {
            if (appUserVo.getState() == 2) {
                Throw.isBusinessException(ResponseEnums.USER_NOT_FOUND);
            }

            if (!StringUtils.isBlank(unionid) && StringUtils.isBlank(appUserVo.getUnionid())) {
                // 同步更新unionid
                if (!appUserApi.updateUnionidById(appUserVo.getId(), unionid).unpack()) {
                    log.error("更新unionid失败，userId：{}， unionid:{}", appUserVo.getId(), unionid);
                }
            }

            UserInfo userInfo = new UserInfo();
            BeanUtils.copyProperties(appUserVo, userInfo);
            String userIdRedis = String.valueOf(appUserVo.getId());

            // 查找商家信息，存在则压入到缓存中
            BusinessPageVo businessPageVo = businessApi.getByUserId(appUserVo.getId()).unpack();
            if (businessPageVo != null) {
                BusinessInfo businessInfo = new BusinessInfo();
                BeanUtils.copyProperties(businessPageVo, businessInfo);
                userInfo.setBusinessInfo(businessInfo);
            }

            // 将用户详细信息缓存到Redis
            redisUtil.set(String.format(RedisKeyConstant.USER_INFO_KEY, userIdRedis), JSON.toJSONString(userInfo), Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

            // 生成随机密钥
            String logKey = StringUtils.getRandomNumString(8);
            // 生成token
            String token = JwtUtil.createToken(logKey, userIdRedis);
            redisUtil.set(String.format(RedisKeyConstant.USER_LOGIN_KEY, userIdRedis), logKey, Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

            // 发送MQ消息，异步添加用户登录记录
            UserLoginRecordMsg userLoginRecordMsg = new UserLoginRecordMsg();
            userLoginRecordMsg.setUserId(appUserVo.getId());
            userLoginRecordMsg.setLoginDate(LocalDateTime.now());
            userLoginRecordMsg.setLoginIp(IpUtil.getIpAddr(request));
            // 登录方式，默认1:手机号登录 2:微信登录
            userLoginRecordMsg.setLoginMode(2);
            userLoginRecordMsg.setCreateTime(LocalDateTime.now());
            userLoginRecordMsgProvider.sendMsg(userLoginRecordMsg);

            Code2SessionVo code2SessionVo = new Code2SessionVo();
            code2SessionVo.setToken(token);
            return code2SessionVo;
        }

        redisUtil.hmSet(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openid), "sessionKey", sessionKey);
        if (!StringUtils.isBlank(unionid)) {
            redisUtil.hmSet(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openid), "unionid", unionid);
        }
        redisUtil.expire(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openid), 30, TimeUnit.MINUTES);

        Code2SessionVo code2SessionVo = new Code2SessionVo();
        code2SessionVo.setOpenId(openid);

        return code2SessionVo;
    }

    public String getWxMobile(WxLoginMobileDto wxLoginMobileDto) {
        String openId = wxLoginMobileDto.getOpenId();
        log.info("getWxMobile----openid:{}", openId);
        Object sessionKeyObj = redisUtil.hGet(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openId), "sessionKey");
        if (sessionKeyObj == null) {
            log.error("getWxMobile注册失败，sessionKey为空");
            Throw.isBusinessException("获取手机号失败");
        }

        JSONObject jsonObject = AES.wxDecrypt(wxLoginMobileDto.getEncryptedData(), (String) sessionKeyObj, wxLoginMobileDto.getIv());
        String phoneNumber = jsonObject.getString("phoneNumber");
        if (StringUtils.isBlank(phoneNumber)) {
            log.error("getWxMobile注册失败，解析手机号为空");
            Throw.isBusinessException("获取手机号失败");
        }

        redisUtil.hmSet(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openId), "mobile", phoneNumber);
        redisUtil.expire(String.format(RedisKeyConstant.REGISTER_WX_OPEN_ID_KEY, openId), 30, TimeUnit.MINUTES);

        return phoneNumber;
    }

    public LoginVo getToken(Long userId, HttpServletRequest request) {
        AppUserVo appUserVo = appUserApi.getById(userId).unpack();
        if (appUserVo == null) {
            Throw.isBusinessException("用户不存在");
        }

        if (appUserVo.getState() == 2) {
            Throw.isBusinessException(ResponseEnums.USER_NOT_FOUND);
        }

        UserInfo userInfo = new UserInfo();
        BeanUtils.copyProperties(appUserVo, userInfo);
        String userIdRedis = String.valueOf(appUserVo.getId());

        // 查找商家信息，存在则压入到缓存中
        BusinessPageVo businessPageVo = businessApi.getByUserId(appUserVo.getId()).unpack();
        if (businessPageVo != null) {
            BusinessInfo businessInfo = new BusinessInfo();
            BeanUtils.copyProperties(businessPageVo, businessInfo);
            userInfo.setBusinessInfo(businessInfo);
        }

        // 将用户详细信息缓存到Redis
        redisUtil.set(String.format(RedisKeyConstant.USER_INFO_KEY, userIdRedis), JSON.toJSONString(userInfo), Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

        // 生成随机密钥
        String logKey = StringUtils.getRandomNumString(8);
        // 生成token
        String token = JwtUtil.createToken(logKey, userIdRedis);
        redisUtil.set(String.format(RedisKeyConstant.USER_LOGIN_KEY, userIdRedis), logKey, Constant.USER_LOGIN_TIMEOUT, TimeUnit.SECONDS);

        // 发送MQ消息，异步添加用户登录记录
        UserLoginRecordMsg userLoginRecordMsg = new UserLoginRecordMsg();
        userLoginRecordMsg.setUserId(Long.parseLong(userIdRedis));
        userLoginRecordMsg.setLoginDate(LocalDateTime.now());
        userLoginRecordMsg.setLoginIp(IpUtil.getIpAddr(request));
        // 登录方式，默认1:手机号登录 2:微信登录
        userLoginRecordMsg.setLoginMode(1);
        userLoginRecordMsg.setCreateTime(LocalDateTime.now());
        userLoginRecordMsgProvider.sendMsg(userLoginRecordMsg);

        LoginVo loginVo = new LoginVo();
        loginVo.setToken(token);

        return loginVo;
    }
}
